How to Customize Default Model Validation Error Messages in ASP.NET Core
TLDR
- ASP.NET Core's default Model Validation messages are provided in English only; they can be customized by creating resource files (.resx).
- Validation messages are divided into "ModelBinding" and "ValidationMetadata" categories, which must be implemented separately.
- By implementing the
IValidationMetadataProviderinterface, you can automatically replace the default error messages ofValidationAttribute. - When supporting multiple languages, set the resource file to "No code generation" and configure
SupportedUICulturesviaRequestLocalizationOptions. - When configuring locales, distinguish between
Culture(formatting) andUICulture(resource loading) to avoid confusion.
Customizing Default Model Validation Messages
When you encounter this issue: When a project requires converting built-in ASP.NET Core validation error messages (such as "The field is required." generated by RequiredAttribute) into Chinese or other languages, and you do not want to manually set error messages for every field.
ASP.NET Core's validation mechanism is divided into two categories:
- ModelBinding Validation: Related to data format (e.g., type conversion failure).
- ValidationMetadata Validation: Related to data content rules (e.g., length limits, required fields).
Creating Resource Files (.resx)
First, create a resource file to store the corresponding error messages. Set the resource file properties as follows:
- Build Action: Embedded Resource
- Copy to Output Directory: Do not copy
Enter the required error message key-value pairs into the resource file, for example, RequiredAttribute_ValidationError mapped to "{0} field is required.".
Creating a Custom ValidationMetadataProvider
To automatically replace the error messages of ValidationAttribute, you need to implement IValidationMetadataProvider.
public class LocalizationValidationMetadataProvider : IValidationMetadataProvider {
private readonly ResourceManager resourceManager;
private readonly Type resourceType;
public LocalizationValidationMetadataProvider(Type type) {
resourceType = type;
resourceManager = new ResourceManager(type);
}
public void CreateValidationMetadata(ValidationMetadataProviderContext context) {
foreach (var attribute in context.ValidationMetadata.ValidatorMetadata.OfType<ValidationAttribute>()) {
if (attribute.ErrorMessageResourceName is null) {
bool hasErrorMessage = attribute.ErrorMessage != null;
if (hasErrorMessage) {
string? defaultErrorMessage = typeof(ValidationAttribute)
.GetField("_defaultErrorMessage", BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(attribute) as string;
hasErrorMessage = attribute.ErrorMessage != defaultErrorMessage;
}
if (hasErrorMessage) {
continue;
}
string? name = GetMessageName(attribute);
if (name != null && resourceManager.GetString(name) != null) {
attribute.ErrorMessageResourceType = resourceType;
attribute.ErrorMessageResourceName = name;
attribute.ErrorMessage = null;
}
}
}
}
private string? GetMessageName(ValidationAttribute attr) {
switch (attr) {
case CompareAttribute _:
return "CompareAttribute_MustMatch";
case StringLengthAttribute vAttr:
if (vAttr.MinimumLength > 0) {
return "StringLengthAttribute_ValidationErrorIncludingMinimum";
}
return "StringLengthAttribute_ValidationError";
case DataTypeAttribute _:
return $"{attr.GetType().Name}_Invalid";
case ValidationAttribute _:
return $"{attr.GetType().Name}_ValidationError";
}
return null;
}
}Registering Services
Register the provider mentioned above in Program.cs and configure the ModelBindingMessageProvider.
builder.Services.AddRazorPages()
.AddMvcOptions(options => {
var provider = options.ModelBindingMessageProvider;
provider.SetAttemptedValueIsInvalidAccessor((x, y) => string.Format(ModelBindingMessage.AttemptedValueIsInvalid, x, y));
// Other ModelBinding message settings...
options.ModelMetadataDetailsProviders.Add(new LocalizationValidationMetadataProvider(typeof(ValidationMetadataMessage)));
});Multi-language Support
When you encounter this issue: When the application needs to dynamically switch the language of validation error messages based on the user's locale settings.
Configuring Language Resource Files
Create resource files for the corresponding languages (e.g., ValidationMetadataMessage.zh-TW.resx) and set their access modifier to "No code generation". The system will automatically read the corresponding resource file based on the UICulture.
Configuring RequestLocalization
Configure RequestLocalizationOptions in Program.cs.
WebApplication app = builder.Build();
string[] supportedCultures = new string[] { "zh-TW", "en-US" };
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);INFO
Common Misconceptions about Culture
- How DefaultRequestCulture works: It is the provider with the lowest priority. The system will attempt to find the
UICulturefrom the QueryString, Cookie, or Accept-Language Header in order; it will only useDefaultRequestCultureif none are found. - Difference between Culture and UICulture:
Culture: Controls the formatting and sorting of dates, numbers, and currency.UICulture: Controls which language resource file is loaded.- If setting the language via QueryString, the correct parameter name should be
ui-culturerather thanculture.
Change Log
- 2022-10-05 Initial document creation.
- 2024-04-04 Fixed
ModelBindingMessagemessages.
